#include <cstdio>
#include <cstring>

#include <iostream>

#include <QWidget>
#include <QMouseEvent>
#include <QFileDialog>
#include <QCoreApplication>

#include <opencv2/highgui/highgui.hpp>
using namespace cv;

#include "remoteimageview.h"

using namespace std;

static inline void setPosRangeScale(int &v, int max, int ori_max){
    if(v < 0) v = 0;
    else if( v > max) v = max;
    v = v * ori_max / max;
}

static void adjustPoints(Point2i &pt1, Point2i &pt2, int width, int height, int ori_width, int ori_height){
    int x1, x2, y1, y2;
    x1 = pt1.x;
    x2 = pt2.x;
    y1 = pt1.y;
    y2 = pt2.y;
    setPosRangeScale(x1, width, ori_width);
    setPosRangeScale(x2, width, ori_width);
    setPosRangeScale(y1, height, ori_height);
    setPosRangeScale(y2, height, ori_height);
    pt1.x = x1 < x2 ? x1: x2;
    pt1.y = y1 < y2 ? y1: y2;
    pt2.x = x2 > x1 ? x2: x1;
    pt2.y = y2 > y1 ? y2: y1;
}

RemoteImageView::RemoteImageView(QWidget *parent) :
    ImageView(parent)
{
    QObject::connect(&this->m_transfer, SIGNAL(gotResponse(QByteArray)), this, SLOT(recvResponse(QByteArray)));
    QObject::connect(&this->m_transfer, SIGNAL(gotImageData(QByteArray)), this, SLOT(recvImageData(QByteArray)));
    QObject::connect(this, SIGNAL(setTransferServer(QHostAddress,quint16)), &this->m_transfer, SLOT(setServer(QHostAddress,quint16)));
    QObject::connect(this, SIGNAL(imgRequest(int,int,int,int,int)), &this->m_transfer, SLOT(imgRequest(int,int,int,int,int)));
    QObject::connect(this, SIGNAL(ptzRequest(int,int,int)), &this->m_transfer, SLOT(ptzRequest(int,int,int)));
    QObject::connect(this, SIGNAL(trackRequest(int,int,int,int,int)), &this->m_transfer, SLOT(trackRequest(int,int,int,int,int)));
    QObject::connect(&this->m_req_timer, SIGNAL(timeout()), this, SLOT(reqestTimerAction()));
    QObject::connect(&this->m_stat_timer, SIGNAL(timeout()), this, SLOT(statisticsTimer()));
    this->m_last_ans_ts.set(-1);
    this->m_transfer_state = false;
    this->m_req_timer.setInterval(250);
    this->m_req_timer.start();
    this->m_img_ts.set(0);
    this->m_img_size = -1;
    this->m_img_data_offset = -1;
    this->m_img_data.clear();
    this->m_img_temp.clear();
    this->m_img_seq = -1;
    this->m_img_total_seq = -1;
    this->m_img_once_count = 1;

    this->m_stat_timer.setInterval(STATISTICS_INTERVAL);
    this->m_sec = STATISTICS_INTERVAL / 1000;
    this->m_stat_timer.start();

    //for display mutex
    this->m_display_new = true;
    this->m_mouse_pressed = false;
    this->m_show_selected = false;


    //for address
    this->m_addr_str = "127.0.0.1";

    this->m_addr.setAddress("127.0.0.1");
    emit setTransferServer(this->m_addr, 7777);

    //for save file
    this->m_save_folder = QCoreApplication::applicationDirPath();
    emit showSaveFolder(QCoreApplication::applicationDirPath());
    this->m_save_state = false;

    //for statistics
    this->m_frame_cost = -1;
}

void RemoteImageView::statisticsTimer(){
    emit frameRate(1000 / this->m_frame_cost);
}

void RemoteImageView::reqestTimerAction(){
    if(this->m_req_mutex.tryLock()){
        if(this->m_transfer_state){

            this->m_req_times ++;

            emit imgRequest((int)this->m_img_ts.toInt(), this->m_img_seq, this->m_ori_width, this->m_ori_height, this->m_img_once_count);
        }
        this->m_req_mutex.unlock();
    }
//    this->m_req_mutex.lock();
//    if(this->m_transfer_state){

//        this->m_req_times ++;

//        emit imgRequest((int)this->m_img_ts.toInt(), this->m_img_seq, this->m_display_width, this->m_display_height, this->m_img_once_count);
//    }
//    this->m_req_mutex.unlock();

}

void RemoteImageView::setServer(QString addr, int port){
     QHostAddress address;
     address.setAddress(addr);
     emit setTransferServer(address, port);
}

void RemoteImageView::changeTargetDirection(int x, int y, int z){
    emit ptzRequest(x, y, z);
}

void RemoteImageView::transferState(int s){
    this->m_req_mutex.lock();
    if (s > 0){
        this->m_img_seq = -1;
        this->m_transfer_state = true;
    }else{
        this->m_transfer_state = false;
    }
    this->m_req_mutex.unlock();
}

void RemoteImageView::resetImg(unsigned int timestamp, int size){

//    qDebug()<<timestamp;

    this->m_img_ts.set(timestamp);
    this->m_img_size = size;
    this->m_img_data.resize(this->m_img_size);
    this->m_img_data_offset = 0;
    this->m_img_seq = 0;
    this->m_img_temp.clear();
    this->m_img_total_seq = (this->m_img_size + RESPONSE_DATA_LEN - 1) / RESPONSE_DATA_LEN;
    this->m_last_data_ts.set(timestamp);
}

void RemoteImageView::restructImg(unsigned char *data, int size, int seq){
    if(seq == this->m_img_seq ){
        int len = size;
        if(this->m_img_data_offset + len > this->m_img_size){
            fprintf(stderr, "image data overflow!\n");
            len = this->m_img_size - this->m_img_data_offset;
        }
        memcpy(&this->m_img_data[this->m_img_data_offset], data, len);
        this->m_img_data_offset += len;
        this->m_img_seq += 1;
        emit imageTransferProgress(this->m_img_seq * 100 / (this->m_img_total_seq));
        if(this->m_img_seq == this->m_img_total_seq){
            this->m_img = imdecode(this->m_img_data, 1);

            saveImg2Video(this->m_img);
            this->m_display_mutex.lock();
            if(this->m_display_new){
                this->m_ori_width = this->m_img.cols;
                this->m_ori_height = this->m_img.rows;
                this->m_display_ts = this->m_img_ts;
                this->showImage(this->m_img);
                if(this->m_show_selected){
                    this->showSelectedRect(this->m_track_pt1, this->m_track_pt2, Scalar(255, 0, 0));
                }else{
                    this->showSelectedRect(this->m_track_ans_pt1, this->m_track_ans_pt2, Scalar(0, 255, 0));
                }
            }
            this->m_display_mutex.unlock();

            this->m_req_mutex.lock();
            this->m_img_ts.set(0);
            this->m_img_seq = -1;
            this->m_req_mutex.unlock();

            //for statistics
            this->m_frame_cost = TimeStamp::now()->diff(this->m_frame_ts);
            this->m_frame_ts.setNow();
            emit imgRequest(0, -1 , this->m_img_width, this->m_img_height, this->m_img_once_count);


        }else{
//            emit imgRequest(this->m_img_ts.toInt(), this->m_img_seq , this->m_img_width, this->m_img_height, this->m_img_once_count);
        }
    }

}

void RemoteImageView::recvResponse(QByteArray response){
    response_head_t* ans = (response_head_t*) response.data();
    if(ans->magic != RESPONSE_MAGIC)//check magic
        return;

    switch(ans->type){
    case IMAGE_RESPONSE:
    {
        unsigned int timestamp = (unsigned int)getResponseField(ans, IMAGE_TIMESTAMP);

        int size = getResponseField(ans, IMAGE_SIZE);
        resetImg(timestamp, size);
    }
        break;
    case PTZ_RESPONSE:
    {
        int x = getResponseField(ans, PTZ_X);
        int y = getResponseField(ans, PTZ_Y);
        int z = getResponseField(ans, PTZ_Z);
        emit changeDirection(x, y, z);
    }
        break;
    case CAMERA_RESPONSE:
    {
    }
        break;
    case TRACK_RESPONSE:
    {

        this->m_track_ans_pt1 = Point2i(getResponseField(ans, TRACK_PT_X1), getResponseField(ans, TRACK_PT_Y1));
        this->m_track_ans_pt2 = Point2i(getResponseField(ans, TRACK_PT_X2), getResponseField(ans, TRACK_PT_Y2));
        this->m_track_center = Point2i(getResponseField(ans, TRACK_PT_CENTER_X), getResponseField(ans, TRACK_PT_CENTER_Y));
        adjustPoints(this->m_track_ans_pt1, this->m_track_ans_pt2, this->m_ori_width, this->m_ori_height, this->width(), this->height());
        this->m_track_ts.set((unsigned int)getResponseField(ans, TRACK_TIMESTAMP));
    }
        break;
    default:
        break;
    }


//    qDebug()<<TimeStamp::now()->toInt()<<" response ts = "<< ans->img_timestamp<<" size = "<<ans->img_size<<" has image ="<<ans->has_img;

}

void RemoteImageView::recvImageData(QByteArray image_data){

    response_data_t* ans = (response_data_t*) image_data.data();

    if(responseInfo(ans)->type != IMAGE_RESPONSE){
        qDebug()<<"do not accept other response!";
        return;
    }


    unsigned int timestamp = (unsigned int) getResponseDataInfo(ans, IMAGE_TIMESTAMP);
    int seq = getResponseDataInfo(ans, IMAGE_SEQ);
    int seq_size = getResponseDataInfo(ans, IMAGE_SEQ_SIZE);

    if (this->m_img_ts != timestamp){
        if(this->m_last_data_ts > responseInfo(ans)->timestamp){//just old data
            return;
        }else{//sever has changed img, than request a new img
            imgRequest(0, -1, this->m_img_width, this->m_img_height, this->m_img_once_count);
            return;
        }
    }

    if (getResponseDataInfo(ans, IMAGE_SEQ) == this->m_img_seq){
        restructImg(responseDataPtr(ans), seq_size, seq);
        for (int i = this->m_img_seq; i < this->m_img_total_seq; ++i){
            if(this->m_img_temp.find(i) != this->m_img_temp.end()){
                restructImg(&this->m_img_temp[i][0], this->m_img_temp[i].size(), i);
                this->m_img_temp.erase(i);
            }else{
                break;
            }
        }
    }else{
        vector<unsigned char> temp;
        temp.resize(seq_size);
        memcpy(&temp[0], &ans->data[0], seq_size);
        this->m_img_temp[seq] = temp;
    }



}


void RemoteImageView::mousePressEvent(QMouseEvent *event){
    if(event->button() == Qt::LeftButton){
        this->m_display_mutex.lock();
        this->m_mouse_pressed = true;
        this->m_show_selected = true;
//        this->m_display_new = false;
        this->m_display_mutex.unlock();
//        qDebug()<<"pressed"<<event->pos();
        this->m_track_pt1.x = event->x();
        this->m_track_pt1.y = event->y();
    }
}



void RemoteImageView::mouseReleaseEvent(QMouseEvent *event){
    if(event->button() == Qt::LeftButton){
        this->m_display_mutex.lock();
        this->m_mouse_pressed = false;
        this->m_show_selected = false;
//        this->m_display_new = true;
        this->m_display_mutex.unlock();
//        qDebug()<<"released"<<event->pos();
        this->m_track_pt2.x = event->x();
        this->m_track_pt2.y = event->y();
        adjustPoints(this->m_track_pt1,
                     this->m_track_pt2,
                     this->width(),
                     this->height(),
                     this->m_ori_width,
                     this->m_ori_height);
        emit trackRequest((int)this->m_display_ts.toInt(),
                          this->m_track_pt1.x,
                          this->m_track_pt1.y,
                          this->m_track_pt2.x,
                          this->m_track_pt2.y);
//        qDebug()<<"("<<this->m_track_pt1.x<<","
//               <<(int)this->m_track_pt1.y<<")";
//        qDebug()<<"("<<this->m_track_pt2.x<<","
//               <<this->m_track_pt2.y<<")";
    }
}

void RemoteImageView::mouseMoveEvent(QMouseEvent *event){
    this->m_display_mutex.lock();
    if(this->m_mouse_pressed){
        this->m_track_pt2.x = event->x();
        this->m_track_pt2.y = event->y();
        this->showSelectedRect(this->m_track_pt1,this->m_track_pt2, Scalar(255, 0, 0));
    }
    this->m_display_mutex.unlock();
}

void RemoteImageView::setAddressString(QString addr){
    this->m_addr_str = addr;
    qDebug()<<addr;
}

void RemoteImageView::setAddress(){
    QHostInfo host_info = QHostInfo::fromName(this->m_addr_str);
    this->m_addr = host_info.addresses().first();
    emit setTransferServer(this->m_addr, 7777);
}

void RemoteImageView::setSaveFolder(){
    this->m_save_folder = QFileDialog::getExistingDirectory(0, QString(), this->m_save_folder);
    emit showSaveFolder(this->m_save_folder);
}

void RemoteImageView::setSaveState(int s){
    this->m_save_mutex.lock();
    if(s > 0){
        this->m_save_state = true;
        this->startSaveVideo();
    }else{
        this->m_save_state = false;
        this->stopSaveVideo();
    }
    this->m_save_mutex.unlock();
}

void RemoteImageView::startSaveVideo(){
    if(!this->m_img.empty()){
        string filename = this->m_save_folder.toStdString() + "/" + this->m_img_ts.toFileName() + ".avi";
        m_save_writer = VideoWriter(filename,
                                    CV_FOURCC('X','V','I','D'),
                                    30,
                                    Size(this->m_img.cols, this->m_img.rows),
                                    true);
    }else{
        qDebug()<<"no image now, wait the first image to record";
    }
}

void RemoteImageView::stopSaveVideo(){
    if(this->m_save_writer.isOpened()){
        this->m_save_writer = VideoWriter();
    }
}

void RemoteImageView::saveImg2Video(Mat img){
    this->m_save_mutex.lock();

    if(this->m_save_state){
        if(!this->m_save_writer.isOpened()){
            string filename = this->m_save_folder.toStdString() + "/" + this->m_img_ts.toFileName() + ".avi";
            this->m_save_writer.open(filename,
                                     CV_FOURCC('X','V','I','D'),
                                     30,
                                     Size(this->m_img.cols, this->m_img.rows),
                                     true);
        }
        this->m_save_writer<<img;
    }
    this->m_save_mutex.unlock();
}


